package org.offsync.nio

import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.SocketAddress
import java.nio.channels.{SelectionKey => JavaSelectionKey}
import java.nio.channels.{ServerSocketChannel => JavaServerSocketChannel}
import org.offsync.rich._
import org.offsync.util.AutoDisposed
import org.offsync.util.DisposableBase

case class ServerSocketChannel(
    protected override val channel: JavaServerSocketChannel)
    extends SelectableChannel(channel) {
  def accept(ioService: IoService)(f: (() => SocketChannel) => Unit): Unit = {
    val acceptOnce = onceOnly { result =>
      result match {
        case Some(e: Throwable) => f { () => throw e }
        case _ => f { () =>
          val subChannel = channel.accept()
          subChannel.configureBlocking(false)
          SocketChannel(subChannel)
        }
      }
    }
    onCanAccept(ioService)(acceptOnce)
  }

  def accept(selector: Selector)(f: SocketChannel => Unit): Unit = {
    channel.register(
        selector.selector,
        JavaSelectionKey.OP_READ,
        {
          val socketChannel = channel.accept()
          socketChannel.configureBlocking(false)
          f(SocketChannel(socketChannel))
        })
  }
  
  def doAccept(): SocketChannel = {
    val clientChannel = channel.accept()
    clientChannel.configureBlocking(false)
    SocketChannel(clientChannel)
  }
  
  def socket: ServerSocket = channel.socket()
  
  def bind(
      socketAddress: SocketAddress) = socket.bind(socketAddress)
}

object ServerSocketChannel {
  def open(): ServerSocketChannel = {
    JavaServerSocketChannel.open().asDisposable.auto { autoChannel =>
      val channel = autoChannel.ref.closeable
      channel.configureBlocking(false)
      ServerSocketChannel(autoChannel.release().closeable)
    }
  }

  def open(host: String, port: Int): ServerSocketChannel = {
    this.open().auto { channel =>
      channel.ref.configureBlocking(false)
      channel.ref.socket.bind(new InetSocketAddress(host, port))
      return channel.release()
    }
  }
}
